package coupon

import (
	"context"
	"encoding/json"
	"log"
	"sale-admin/config/redis"
	"sale-admin/internal/app/web/service/common"
	"sale-admin/internal/dal"
	"sale-admin/internal/dao"
	"sale-admin/internal/define"
	"sale-admin/internal/models"
	"strconv"
	"time"

	"192.168.1.75/go-pkg/errorx"
	"192.168.1.75/go-pkg/logx"
	"github.com/samber/lo"
	"github.com/shopspring/decimal"
)

type CouponCode struct{}

// List 优惠码列表
func (CouponCode) List(
	ctx context.Context,
	batchID string,
	status int,
	page, limit int,
) (records []dao.CouponCodeListType, total int64, err error) {
	if !(dao.CouponBatch{}).Exists(ctx, dal.Q, batchID) {
		return []dao.CouponCodeListType{}, 0, errorx.New("无效的批次id", -1)
	}
	return dao.CouponCode{}.List(ctx, dal.Q, batchID, define.StatusType(status), page, limit)
}

// 订单商品信息
type OrderGoodsInfoT struct {
	ClassID      string  `json:"ClassID"`      // 商品分类
	GoodsID      string  `json:"GoodsID"`      // 商品id
	GoodsName    string  `json:"GoodsName"`    // 商品名称
	Univalent    float64 `json:"Univalent"`    // 单价
	ContainerNum string  `json:"ContainerNum"` // 货道号
}

// 出货信息
type DeliveringInfoT struct {
	ClassID         string  `json:"class_id"`           // 商品分类
	GoodsID         string  `json:"goods_id"`           // 商品id
	GoodsName       string  `json:"goods_name"`         // 商品名称
	Univalent       float64 `json:"univalent"`          // 单价
	Floor           int     `json:"floor"`              // 货层
	ContainerNum    string  `json:"container_num"`      // 货道号
	OutTradeNo      string  `json:"out_trade_no"`       // 交易号
	ChildOutTradeNo string  `json:"child_out_trade_no"` // 子交易号
}

// Check 优惠码验证
// 前端需要根据返回的商品id
func (CouponCode) Check(
	ctx context.Context,
	salesID string,
	orderAount float64,
	code string,
	useCase int, // 使用场景,1: 优惠场景  2: 兑换/取货场景
	Online int, // 0: 线下 1: 在线
) (deliveringInfo []*DeliveringInfoT, goodsIDs []string, reduce float64, discount float64, discountAmount float64, err error) {
	// 默认为兑换/取货场景
	if useCase == 0 {
		useCase = 2
	}
	robotDetail, _ := dao.Robot{}.Detail(ctx, dal.Q, "", salesID)
	if robotDetail == nil {
		return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("无效的设备", -1)
	}
	// 获取码详情
	codeDetail, err := dao.CouponCode{}.Detail(ctx, dal.Q, code)
	if codeDetail == nil || err != nil {
		return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("无效的码", -1)
	}
	// 验证码是否可以使用
	if codeDetail.Status == int(define.CouponCodeStatus_Used) {
		return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("码已使用", -1)
	}
	// 验证码的状态
	if codeDetail.Status == int(define.CouponCodeStatus_Invalid) {
		return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("码已失效", -1)
	}
	// 验证码的有效期[批次的有效期]
	timeNow := time.Now().Unix()
	if !(codeDetail.ValidityStartTime <= timeNow && timeNow <= codeDetail.ValidityEndTime) {
		return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("码不在有效期", -1)
	}
	// 判断码是否过期[码单独设置的过期时间]
	if codeDetail.ExpiredAt > 0 && codeDetail.ExpiredAt < time.Now().Unix() {
		return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("码已过期", -1)
	}
	// 获取批次详情
	batchDetail, _ := dao.CouponBatch{}.Detail(ctx, dal.Q, codeDetail.BatchID)
	if batchDetail == nil {
		return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("活动无效", -1)
	}
	// 判断是否为兑换场景
	if (batchDetail.CodeType == 3 || batchDetail.CodeType == 4) && useCase != 2 {
		return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("不适用兑换场景", -1)
	}
	// 针对取货码
	// 通过子码获取订单信息，再通过订单信息，获取出货信息，从而
	if batchDetail.CodeType == 4 {
		// 获取子码信息
		childCodeRecord, _ := dao.CouponChildCode{}.Take(ctx, dal.Q, define.CouponChildCodeStatus_Locked, codeDetail.Code, "")
		if childCodeRecord == nil {
			return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("无效的取货码", -1)
		}
		// 获取订单列表
		orderList, _ := dao.NewOrder(ctx, dal.Q).WithOutTradeNoCond(childCodeRecord.OutTradeNo).List()
		if len(orderList) == 0 {
			return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("无效的取货码", -1)
		}
		// 取货机器与预约机器校验
		robotID := orderList[0].RobotName
		robotRecord, _ := dao.Robot{}.Detail(ctx, dal.Q, robotID, "")
		if robotRecord.SalesID != salesID {
			return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("请到预约的机器上取货", -1)
		}
		// 获取每个子单的出货信息
		for _, orderRecord := range orderList {
			if orderRecord.OrderStatus != int(define.OrderStatus_Paid) {
				continue
			}
			orderGoodsInfo := []OrderGoodsInfoT{}
			err := json.Unmarshal([]byte(orderRecord.CountData), &orderGoodsInfo)
			if err != nil || len(orderGoodsInfo) == 0 {
				continue
			}
			ContainerNum, _ := strconv.Atoi(orderGoodsInfo[0].ContainerNum)
			deliveringInfo = append(deliveringInfo, &DeliveringInfoT{
				ClassID:         orderGoodsInfo[0].ClassID,
				GoodsID:         orderGoodsInfo[0].GoodsID,
				GoodsName:       orderGoodsInfo[0].GoodsName,
				Univalent:       orderGoodsInfo[0].Univalent,
				Floor:           (ContainerNum / 10),
				ContainerNum:    orderGoodsInfo[0].ContainerNum,
				OutTradeNo:      orderRecord.OutTradeNo,
				ChildOutTradeNo: orderRecord.ChildOutTradeNo,
			})
		}
		if len(deliveringInfo) == 0 {
			return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("取货码已使用", -1)
		}
		if Online == 1 {
			deliverinfo, _ := json.Marshal(deliveringInfo)
			redis.NewDB().Set(ctx, define.RedisPickupCode+salesID, string(deliverinfo), 1*time.Minute)
		}
		return deliveringInfo, []string{}, 0, 0, 0, nil
	}
	// 验证是否在允许的区域
	areaIDs, _ := dao.ObjAssociation{}.GetAssociationID(ctx, dal.Q, define.AssociationCouponArea, codeDetail.BatchID, "")
	if len(areaIDs) > 0 {
		provincID := strconv.Itoa(robotDetail.Areas1)
		cityID := strconv.Itoa(robotDetail.Areas2)
		districtID := strconv.Itoa(int(robotDetail.Areas5))
		if !(lo.Contains(areaIDs, provincID) && lo.Contains(areaIDs, cityID) && lo.Contains(areaIDs, districtID)) {
			return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("不在使用区内", -1)
		}
	}
	robotIDs, _ := dao.ObjAssociation{}.GetAssociationID(ctx, dal.Q, define.AssociationCouponRobot, codeDetail.BatchID, "")
	// 验证是否在允许的机器
	if len(robotIDs) > 0 {
		if !lo.Contains(robotIDs, robotDetail.RobotName) {
			return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("不在允许使用的机器范围", -1)
		}
	}
	// 通用码或一人一码获取打折优惠信息
	if codeDetail.CodeType == 1 || codeDetail.CodeType == 2 {
		// 判断是否满足使用门槛
		if batchDetail.OrderAmountRequired == 1 && orderAount > 0 {
			if orderAount < batchDetail.OrderAmount {
				return []*DeliveringInfoT{}, []string{}, 0, 0, 0.00, errorx.New("订单金额不满足", -1)
			}
		}
		// 计算优惠金额
		// 满减的
		if batchDetail.Reduce > 0 && batchDetail.Reduce < orderAount {
			return []*DeliveringInfoT{}, []string{}, batchDetail.Reduce, 0, batchDetail.Reduce, nil
		} else if batchDetail.Discount > 0 && batchDetail.Discount < 10 {
			// 打折的
			discountAmount, _ := decimal.NewFromFloat(1 - batchDetail.Discount/10).
				Mul(decimal.NewFromFloat(orderAount)).
				Round(2).
				Float64()
			if discountAmount >= 0.01 {
				return []*DeliveringInfoT{}, goodsIDs, 0, batchDetail.Discount, discountAmount, nil
			}
		}
	}
	// 获取适用的商品
	goodsIDs, _ = dao.ObjAssociation{}.GetAssociationID(ctx, dal.Q, define.AssociationCouponGoods, batchDetail.ID, "")
	return []*DeliveringInfoT{}, goodsIDs, 0, 0, 0.00, nil
}

// ConsumeLock 核销锁
func (CouponCode) ConsumeLock(
	ctx context.Context,
	code string,
	outTradeNo string,
	expiration time.Duration,
) (err error) {
	if outTradeNo == "" {
		errorx.New("无效的交易号", -1)
	}
	// 异常恢复
	// 涉及订单，避免意外panic，导致不良订单交易
	defer func() {
		if err1 := recover(); err1 != nil {
			log.Println("CouponCode ConsumeLock panic", logx.String("code", code), logx.String("outTradeNo", outTradeNo))
			err = errorx.New("无效的码", -1)
		}
	}()
	rdb := redis.NewDB()
	redisCtx, redisCancel := context.WithTimeout(context.Background(), time.Second*10)
	defer redisCancel()
	// 延迟解锁
	defer rdb.Del(redisCtx, define.RedisCouponLock+code)
	for {
		select {
		case <-redisCtx.Done():
			return errorx.New("验证超时", -1)
		default:
			// 并发锁
			success, _ := rdb.SetNX(redisCtx, define.RedisCouponLock+code, 1, time.Second*10).Result()
			if success {
				codeDetail, _ := dao.CouponCode{}.Detail(ctx, dal.Q, code)
				if codeDetail == nil {
					return errorx.New("无效的码", -1)
				}
				// 获取批次详情
				batchDetail, _ := dao.CouponBatch{}.Detail(ctx, dal.Q, codeDetail.BatchID)
				if batchDetail == nil {
					return errorx.New("无效的码", -1)
				}
				// 无使用限制，跳过子码锁定
				if batchDetail.TotalTimes == -1 {
					return nil
				}
				// 核销锁定
				{
					// 重置锁定超时的子码
					dao.CouponCode{}.ExpirationReset(ctx, dal.Q, code, int64(expiration.Seconds()))
					// 获取一个可用的子码
					record, _ := dao.CouponChildCode{}.Take(ctx, dal.Q, define.CouponChildCodeStatus_UnUsed, code, "")
					// 子码已使用完
					if record == nil {
						return errorx.New("码已使用", -1)
					}
					// 锁定一个子码
					err := dao.CouponChildCode{}.UpdateChildCode(ctx, dal.Q, record.ID, define.CouponChildCodeStatus_Locked, outTradeNo)
					if err != nil {
						return errorx.New("验证失败", -1)
					}
				}
				// 生成活动订单
				time.AfterFunc(time.Second*3, func() {
					CouponCode{}.CreateActivityOrder(context.Background(), codeDetail.BatchID, outTradeNo)
				})
				// 订单优惠码锁定标记
				rdb.SetNX(redisCtx, define.RedisCouponCodeLockFlag+outTradeNo, 1, expiration)
				return nil
			}
			time.Sleep(time.Second)
		}
	}
}

// ConsumeConfirm 核销确认
func (CouponCode) ConsumeConfirm(
	ctx context.Context,
	outTradeNo string, // 交易号
) (err error) {
	// 异常恢复
	// 涉及订单，避免意外panic，导致不良订单交易
	defer func() {
		if err1 := recover(); err1 != nil {
			err = errorx.New("无效的码", -1)
			log.Println("CouponCode ConsumeConfirm panic", logx.String("outTradeNo", outTradeNo))
		}
	}()
	rdb := redis.NewDB()
	redisCtx, redisCancel := context.WithTimeout(context.Background(), time.Second*10)
	defer redisCancel()
	// 如果不存在优惠码相关的订单，直接结束
	// 避免不必要查询，减轻mysql服务器的压力
	n, _ := rdb.Exists(redisCtx, define.RedisCouponCodeLockFlag+outTradeNo).Result()
	if n != 1 {
		return nil
	}
	// 获取订单信息
	orderRecords, _ := dao.NewOrder(ctx, dal.Q).WithOutTradeNoCond(outTradeNo).List()
	if orderRecords == nil {
		return errorx.New("无效的订单", -1)
	}
	orderDetail := orderRecords[0]
	// 如果出货未成功
	// 重置优惠码子码的状态
	// 更新优惠码订单的的支付状态
	if orderDetail.OrderStatus != int(define.OrderStatus_Shipment_Success) {
		// 获取码
		childCode, _ := dao.CouponChildCode{}.Take(ctx, dal.Q, define.CouponChildCodeStatus_Locked, "", outTradeNo)
		if childCode == nil {
			return errorx.New("核销确认时，获取码信息失败", -1)
		}
		// 重置优惠码的状态
		dao.CouponCode{}.Reset(ctx, dal.Q, childCode.Code)
		// 更新订单状态
		dao.ActivityOrder{}.UpdateStatus(ctx, dal.Q, outTradeNo, int(orderDetail.PaymentID), orderDetail.OrderStatus)
		return nil
	}
	// 如果出货成功
	// 更新子码状态为已使用
	// 更新批次交易数据
	// 更新主码状态
	// 生成核销日志
	childCodeRecord, _ := dao.CouponChildCode{}.Take(
		ctx,
		dal.Q,
		define.CouponChildCodeStatus_Locked,
		"",
		outTradeNo,
	)
	if childCodeRecord == nil {
		return errorx.New("无效的码号", -1)
	}
	// 更新子码状态
	{
		dao.CouponChildCode{}.UpdateChildCode(
			ctx,
			dal.Q,
			childCodeRecord.ID,
			define.CouponChildCodeStatus_Used,
			outTradeNo,
		)
	}
	// 获取批次详情
	batchDetail, _ := dao.CouponBatch{}.Detail(ctx, dal.Q, childCodeRecord.BatchID)
	if batchDetail == nil {
		return errorx.New("无效的码号", -1)
	}
	// 检查并同步主码的状态
	CouponCode{}.checkAndSyncStatus(ctx, childCodeRecord.Code)
	// 更新活动订单的状态
	dao.ActivityOrder{}.UpdateStatus(ctx, dal.Q, outTradeNo, 14, int(define.OrderStatus_Shipment_Success))
	// 更新交易数据
	{
		// 总交易金额
		totalAmount := decimal.NewFromFloat(batchDetail.TotalAmount + orderDetail.PaymentAmount).Round(2).InexactFloat64()
		// 总打折金额
		totalDiscountAmount := decimal.NewFromFloat(batchDetail.TotalDiscountAmount + orderDetail.DiscountAmount).Round(2).InexactFloat64()
		// 总订单量
		totalOrderNum := batchDetail.TotalOrderNum + 1
		// 客单价
		avgAmount := decimal.NewFromFloat(totalAmount).Div(decimal.NewFromInt(int64(totalOrderNum))).Round(2).InexactFloat64()
		// 计算码的使用量
		totalUsedNum := dao.CouponBatch{}.TotalUsedNum(ctx, dal.Q, batchDetail.ID)
		dao.CouponBatch{}.UpdateTradeData(
			ctx, dal.Q, childCodeRecord.BatchID,
			totalAmount,
			totalDiscountAmount,
			totalOrderNum,
			avgAmount,
			totalUsedNum,
		)
	}
	// 生成核销日志
	dao.CouponLog{}.Create(
		ctx,
		dal.Q,
		batchDetail.ID,
		childCodeRecord.Code,
		outTradeNo,
		orderDetail.MerchantID,
	)
	return nil
}

// CreateActivityOrder 生成活动订单
func (CouponCode) CreateActivityOrder(
	ctx context.Context,
	batchID string,
	outTradeNo string,
) error {
	if outTradeNo == "" {
		return errorx.New("交易号不能为空", -1)
	}
	orders, _ := dao.NewOrder(ctx, dal.Q).WithOutTradeNoCond(outTradeNo).List()
	for _, order := range orders {
		var goodsInfo []struct {
			GoodsID   string  `json:"GoodsID"`
			Univalent float64 `json:"Univalent"`
		}
		var goodsID string
		var goodsPrice float64
		json.Unmarshal([]byte(order.CountData), &goodsInfo)
		if len(goodsInfo) > 0 {
			goodsID = goodsInfo[0].GoodsID
			goodsPrice = goodsInfo[0].Univalent
		}
		dao.ActivityOrder{}.Create(
			context.Background(), dal.Q,
			int(define.ActivityOrderType_Coupon),
			batchID,
			order.OutTradeNo,
			order.ChildOutTradeNo,
			order.RobotName, order.MerchantID,
			strconv.Itoa(order.Areas3), strconv.Itoa(order.Areas1), strconv.Itoa(order.Areas2), strconv.Itoa(int(order.Areas5)),
			goodsID, goodsPrice,
			order.PaymentAmount, order.DiscountAmount,
			int(order.PaymentID), order.OrderStatus,
		)
	}
	return nil
}

// ConsumeLog 消费记录
func (CouponCode) ConsumeLog(
	ctx context.Context,
	merchantID string,
	batchID string,
	code string,
	outTradeNO string,
	startTime, endTime int64,
	page, limit int,
) (records []dao.CouponLogListType, total int64, err error) {
	if !(dao.CouponBatch{}).Exists(ctx, dal.Q, batchID) {
		return []dao.CouponLogListType{}, 0, errorx.New("无效的优惠码批次", -1)
	}
	merchantIDs := []string{}
	if merchantID != "1" {
		merchantIDs, _ = common.GetFamilyMerchants(ctx, merchantID)
	}
	records, total = dao.CouponLog{}.List(
		ctx,
		dal.Q,
		merchantIDs,
		batchID,
		code,
		outTradeNO,
		startTime, endTime,
		page, limit,
	)
	return
}

// checkAndSyncStatus 检查并同步主码的状态
func (CouponCode) checkAndSyncStatus(
	ctx context.Context,
	code string,
) {
	unusedRecord, _ := dao.CouponChildCode{}.Take(ctx, dal.Q, define.CouponChildCodeStatus_UnUsed, code, "")
	usedRecord, _ := dao.CouponChildCode{}.Take(ctx, dal.Q, define.CouponChildCodeStatus_Used, code, "")
	if unusedRecord != nil && usedRecord != nil {
		dao.CouponCode{}.UpdateStatus(ctx, dal.Q, code, define.CouponCodeStatus_Using) // 使用中
	} else if unusedRecord == nil {
		dao.CouponCode{}.UpdateStatus(ctx, dal.Q, code, define.CouponCodeStatus_Used) // 已使用
	}
}

// GetRedemptionCode 获取优惠码
func (CouponCode) GetCode(
	ctx context.Context,
	merchantID string, // 商户id
	batchID string, // 券码批次id
	num int, // 数量
	expiredAt int64, // 设置过期时间
	outTradeNo string, // 交易号
) (codes []string, err error) {
	var codeRecords []dao.CouponCodeListType
	// 判断取货码的批次是否存在，不存在则自动创建取货码批次
	if batchID == "0" {
		if !(dao.CouponBatch{}).Exists(ctx, dal.Q, "0") {
			dao.CouponBatch{}.Create(ctx, dal.Q, &models.CouponBatch{
				ID:                "0", // 内部约定取货码的批次id为0
				Name:              "取货码批次",
				CodeType:          4, // 类型为取货码
				Code:              "",
				TotalNum:          0,
				TotalTimes:        1,
				MerchantID:        merchantID,
				ValidityStartTime: time.Now().Unix(),
				ValidityEndTime:   time.Now().Unix() + 3600*24*365*10, // 有效期默认10年
			})
		}
	}
	var couponBatch *models.CouponBatch
	// 获取批次详情
	couponBatch, _ = dao.CouponBatch{}.Detail(ctx, dal.Q, batchID)
	if couponBatch == nil {
		return []string{}, errorx.New("无效的批次", -1)
	}
	// 判断批次的有效性
	if !(time.Now().Unix() >= couponBatch.ValidityStartTime && time.Now().Unix() <= couponBatch.ValidityEndTime) {
		return []string{}, errorx.New("批次不在有效期", -1)
	}
	// 生成取货码
	if batchID == "0" {
		code, err := CreatePickupCode(ctx, expiredAt, outTradeNo, merchantID)
		return []string{code}, err
	}
	// 获取一个有效的码
	codeRecords, _, _ = dao.CouponCode{}.List(ctx, dal.Q, couponBatch.ID, define.CouponCodeStatus_UnUsed, 1, num)
	if len(codeRecords) <= 0 {
		return []string{}, errorx.New("无库存", -1)
	}
	for _, codeRecord := range codeRecords {
		// 修改码的状态为已发放
		err = dao.CouponCode{}.UpdateStatus(ctx, dal.Q, codeRecord.Code, define.CouponCodeStatus_Issued)
		if err != nil {
			return []string{}, errorx.New("生成码失败", -1)
		}
		// 更新过期时间
		if expiredAt > 0 {
			dao.CouponCode{}.UpdateExpireTime(ctx, dal.Q, codeRecord.Code, expiredAt)
		}
		codes = append(codes, codeRecord.Code)
	}
	return codes, nil
}

// 优惠码退款 Refund
func (CouponCode) Refund(
	ctx context.Context,
	outTradeNo string, // 交易号
) error {
	record, _ := dao.CouponChildCode{}.Take(ctx, dal.Q, define.CouponChildCodeStatus_Used, "", outTradeNo)
	if record == nil {
		return errorx.New("退款失败", -1)
	}
	// 更改子码的状态-未使用
	dao.CouponCode{}.UpdateStatus(ctx, dal.Q, record.Code, define.CouponCodeStatus_Using)
	dao.CouponChildCode{}.UpdateChildCodeStatus(ctx, dal.Q, outTradeNo, define.CouponChildCodeStatus_UnUsed)
	return nil
}
